package com.pervasa.demo.re.impl.command;

import java.util.Calendar;

import javax.swing.JOptionPane;
import com.pervasa.demo.re.impl.ReactivityEngine;
import com.pervasa.demo.re.impl.command.Token.Kind;
import com.pervasa.demo.re.impl.data.Action;
import com.pervasa.demo.re.impl.data.AtomicAction;
import com.pervasa.demo.re.impl.data.AtomicEvent;
import com.pervasa.demo.re.impl.data.ComboAction;
import com.pervasa.demo.re.impl.data.ComboEvent;
import com.pervasa.demo.re.impl.data.Condition;
import com.pervasa.demo.re.impl.data.Event;
import com.pervasa.demo.re.impl.data.Rule;
import com.pervasa.demo.re.impl.data.TFM;
import com.pervasa.demo.re.impl.data.TFMEvent;
import com.pervasa.demo.re.impl.data.TimeWindow;

public class Parser {

	private Scanner s;
	private Token t;
	private Executor executor;
	private ReactivityEngine re;
	private String cmd;

	public Parser(String cmd, ReactivityEngine reactivityEngine,
			Executor executor) {
		this.executor = executor;
		this.re = reactivityEngine;
		this.cmd = cmd;
	}

	public void parse() throws SyntaxException {

		// this.cmd = cmd;
		// System.out.println("Parse command: " + cmd);
		s = new Scanner(cmd);

		consume();

		// System.out.println("Comsume Kind: " + t.kind);

		Kind cmdType = t.kind;

		switch (cmdType) {
		case LIST:
			consume();
			list();
			break;

		case CLEAR:
			consume();
			clear();
			break;

		case BASIC:
			consume();
			basic();
			break;

		case DEFINE:
			consume();
			define();
			break;

		case SET:
			consume();
			set();
			break;

		case RUN:
			consume();
			run();
			break;

		case STOP:
			consume();
			stop();
			break;

		case LOAD:
			consume();
			load();
			break;
			
		case EOF:
			consume();
			break;

		default:
			JOptionPane.showMessageDialog(re, "Invalid usage of command");
			break;
		}
	}

	private void clear() {
		executor.exeClear();
	}

	private void load() throws SyntaxException {
		String filename = "";
		if (t.kind == Kind.IDENT) {
			filename += ((IdentToken) match(Kind.IDENT)).spelling;
		}
		else throw new SyntaxException(t, "");
		executor.exeLoad(filename);
	}

	private void stop() {
		if (isKind(Kind.EOF)) {
			executor.exeStop();
		} else {
			JOptionPane.showMessageDialog(re, "Invalid usage of Run");
		}
	}

	private void run() {
		if (isKind(Kind.EOF)) {
			executor.exeRun();
		} else {
			JOptionPane.showMessageDialog(re, "Invalid usage of Run");
		}
	}

	/* Command = SET condition_name = {TRUE | FALSE} */
	private void set() throws SyntaxException {

		String condName;

		if (isKind(Kind.IDENT)) {
			condName = t.toString();
			System.out.println(condName);

			match(Kind.BECOMES);

			if (isKind(Kind._TRUE) || isKind(Kind._FALSE)) {
				executor.exeSet(condName, t.kind);
			} else {
				JOptionPane.showMessageDialog(re, "Invalid usage of SET");
			}
		} else {
			JOptionPane.showMessageDialog(re, "Invalid usage of SET");
		}

	}

	/*
	 * Command = DEFINE {	event event_name = E |
	 * 						condition condition_name = C |
	 * 						action action_name = A |
	 * 						rule rule_name = RULE
	 * 					}
	 */
	private void define() throws SyntaxException {
		switch (t.kind) {
		case EVENT:
			match(Kind.EVENT);
			String eventName = ((IdentToken) match(Kind.IDENT)).spelling;
			match(Kind.BECOMES);
			Event event = event(eventName);
			executor.exeDefine(eventName, event);
			break;
		case ACTION:
			match(Kind.ACTION);
			String actionName = ((IdentToken) match(Kind.IDENT)).spelling;
			match(Kind.BECOMES);
			Action action = action(actionName);
			executor.exeDefine(actionName, action);
			break;
		case RULE:
			match(Kind.RULE);
			String ruleName = ((IdentToken) match(Kind.IDENT)).spelling;
			match(Kind.BECOMES);
			Rule rule = rule(ruleName);
			executor.exeDefine(ruleName, rule);
			break;
		case CONDITION:
			match(Kind.CONDITION);
			String condName = ((IdentToken) match(Kind.IDENT)).spelling;;
			match(Kind.BECOMES);
			Condition cond = condition(condName);
			executor.exeDefine(condName, cond);
			break;
		default:
			throw new SyntaxException(t, "Error parameter for DEFINE command.");
		}
	}

	/*
	 * C = { TRUE | FALSE }
	 */
	private Condition condition(String condName) throws SyntaxException {
		
		if (t.kind == Kind._FALSE) {
			return new Condition(condName, false);
		}
		else if (t.kind == Kind._TRUE) {
			return new Condition(condName, true);
		}
		else {
			throw new SyntaxException(t, "Parsing Error: true or false is excepted.");
		}
	}

	/*
	 * RULE = \\E,C,A\\
	 * Add a new syntax for convenient:
	 * RULE = E,C,A
	 */
	private Rule rule(String ruleName) throws SyntaxException {
		Event event = null;
		String condName = null;
		String actionName = null;
		switch (t.kind) {
		case BACKDASH:
			consume();
			event = event(null);
			match(Kind.COMMA);
			condName = ((IdentToken) match(Kind.IDENT)).spelling;
			match(Kind.COMMA);
			actionName = ((IdentToken) match(Kind.IDENT)).spelling;
			match(Kind.BACKDASH);
			break;
		case IDENT:
			event = event(null);
			match(Kind.COMMA);
			condName = ((IdentToken) match(Kind.IDENT)).spelling;
			match(Kind.COMMA);
			actionName = ((IdentToken) match(Kind.IDENT)).spelling;
			break;
		default:
			throw new SyntaxException(t, "Parsing Error: invalid usage for DEFINE rule command.");
		}
		return new Rule(ruleName, event, re.getRuntimeConditions().get(condName), re.getRuntimeActions().get(actionName), re);
	}

	/*
	 * A = Service.method; |
	 *     (A; A)
	 */
	private Action action(String actionName) throws SyntaxException {
		switch (t.kind) {
		case LPAREN:
			match(Kind.LPAREN);
			ComboAction action = new ComboAction(actionName, re);
			String actionName1 = ((IdentToken) match(Kind.IDENT)).spelling;
			action.addAction(re.getRuntimeActions().get(actionName1));
			while (t.kind == Kind.SEMI) {
				match(Kind.SEMI);
				String actionName2 = ((IdentToken) match(Kind.IDENT)).spelling;
				action.addAction(re.getRuntimeActions().get(actionName2));
			}
			
			match(Kind.RPAREN);
			return action;
		case IDENT:
			String actuatorName = ((IdentToken) match(Kind.IDENT)).spelling;
//			match(Kind.DOT);
//			String methodName = ((IdentToken) match(Kind.IDENT)).spelling;
			match(Kind.LPAREN);
			int val = 1;
			if (t.kind == Kind.MINUS) {
				match(Kind.MINUS);
				val = -1;
			}
			val *= ((IntLitToken) match(Kind.INT_LITERAL)).val;
			match(Kind.RPAREN);
			return new AtomicAction(actionName, re.getActuatorMap().get(actuatorName), null, val, re);
		default:
			throw new SyntaxException(t, "Parsing error: Service name or left paren is expected.");
		}
	}

	/*
	 * E = Sensor(value) | Sensor[a,b] | E + E | E * E | E * seconds * E | <W, Fe, Fr>(E)
	 */
	private Event event(String eventName) throws SyntaxException {
		if (t.kind == Kind.LANGLE) {
			match(Kind.LANGLE);
			TimeWindow timeWindow = timeWindow();
			int fe = 0;
			match(Kind.COMMA);
			if (t.kind == Kind.INT_LITERAL) {
				fe = ((IntLitToken) match(Kind.INT_LITERAL)).val;
			}
			else if (t.kind == Kind.INF) {
				match(Kind.INF);
				fe = -1;
			}
			else {
				throw new SyntaxException(t, "");
			}
			match(Kind.COMMA);
			int fr = 0;
			if (t.kind == Kind.INT_LITERAL) {
				fr = ((IntLitToken) match(Kind.INT_LITERAL)).val;
			}
			else if (t.kind == Kind.INF) {
				match(Kind.INF);
				fr = -1;
			}
			
			boolean isPercentage = false;
			if (t.kind == Kind.PERCENT) {
				isPercentage = true;
				match(Kind.PERCENT);
			}
			
			match(Kind.RANGLE);
			match(Kind.LPAREN);
			String ident = ((IdentToken) match(Kind.IDENT)).spelling;
			match(Kind.RPAREN);
			
			return new TFMEvent(eventName, re.getEventList().get(ident), new TFM(timeWindow, fe, fr, isPercentage));
			
		}
		
		String ident = ((IdentToken) match(Kind.IDENT)).spelling;
			
		Event event = null;
		switch (t.kind) {
		case LPAREN:
			consume();
			int val = 1;
			if (t.kind == Kind.MINUS) {
				match(Kind.MINUS);
				val = -1;
			}
			val *= ((IntLitToken) match(Kind.INT_LITERAL)).val;
			if (t.kind == Kind.COMMA) {
				match(Kind.COMMA);
				int val2 = 1;
				if (t.kind == Kind.MINUS) {
					match(Kind.MINUS);
					val2 = -1;
				}
				val2 *= ((IntLitToken) match(Kind.INT_LITERAL)).val;
				event = new AtomicEvent(eventName, re.getSensorMap().get(ident), val, val2, re);
			}
			else 
				event = new AtomicEvent(eventName, re.getSensorMap().get(ident), val, val, re);
			match(Kind.RPAREN);
			break;
		case LSQUARE:
			consume();
			int lowerBound = 1;
			if (t.kind == Kind.MINUS) {
				match(Kind.MINUS);
				lowerBound = -1;
			}
			lowerBound *= ((IntLitToken) match(Kind.INT_LITERAL)).val;
			match(Kind.COMMA);
			int upperBound = 1;
			if (t.kind == Kind.MINUS) {
				match(Kind.MINUS);
				upperBound = -1;
			}
			upperBound *= ((IntLitToken) match(Kind.INT_LITERAL)).val;
			match(Kind.RSQUARE);
			event = new AtomicEvent(eventName, re.getSensorMap().get(ident), lowerBound, upperBound, re);
			break;
		case PLUS:
			match(Kind.PLUS);
			Event plusEvent2 = event(null);
			event = new ComboEvent(eventName, re.getEventList().get(ident), plusEvent2, ComboEvent.Relation.OR, re);
			break;
		case TIMES:
			Event prevEvent1 = re.getEventList().get(ident);
			Event prevEvent2 = null;
			while (t.kind == Kind.TIMES)
			{
				match(Kind.TIMES);
				switch (t.kind) {
				case INT_LITERAL:
					int time = ((IntLitToken) match(Kind.INT_LITERAL)).val;
					match(Kind.TIMES);
					String delayEvent2Name = ((IdentToken) match(Kind.IDENT)).spelling;
					if (prevEvent2 != null) {
						prevEvent1 = new ComboEvent(null, prevEvent1, prevEvent2, time, re); 
					}
					prevEvent2 = re.getEventList().get(delayEvent2Name);
					break;
				case IDENT:
					String timesEvent2Name = ((IdentToken) match(Kind.IDENT)).spelling;
					if (prevEvent2 != null) {
						prevEvent1 = new ComboEvent(null, prevEvent1, prevEvent2, ComboEvent.Relation.AND, re); 
					}
					prevEvent2 = re.getEventList().get(timesEvent2Name);
					break;
				default:
					throw new SyntaxException(t, "Parsing Error: Event name or time for delay is expected.");
				}
			}
			event = new ComboEvent(eventName, prevEvent1, prevEvent2, ComboEvent.Relation.AND, re);
			break;
		default:
			event = re.getEventList().get(ident);
		}
		
		return event;
	}

	private TimeWindow timeWindow() throws SyntaxException {
		switch (t.kind) {
		case NIL:
			match(Kind.NIL);
			return new TimeWindow();
		case INT_LITERAL:
			Calendar time1 = Calendar.getInstance();
			Calendar time2 = Calendar.getInstance();
			time(time1);
			match(Kind.MINUS);
			boolean isRecur = time(time2);
			return new TimeWindow(time1, time2, isRecur);
		default:
			throw new SyntaxException(t, "");
		}
	}
	
	private boolean time(Calendar calendar) throws SyntaxException {
		int val1 = ((IntLitToken) match(Kind.INT_LITERAL)).val, val2 = 1, val3 = 1, val4 = 2000;
		boolean flag = false;
		switch (t.kind) {
		case SLASH:
			match(Kind.SLASH);
			val2 = ((IntLitToken) match(Kind.INT_LITERAL)).val;
			match(Kind.SLASH);
			val3 = ((IntLitToken) match(Kind.INT_LITERAL)).val;
			match(Kind.SLASH);
			val4 = ((IntLitToken) match(Kind.INT_LITERAL)).val;
			flag = true;
		case COLON:
			match(Kind.COLON);
			int val5 = ((IntLitToken) match(Kind.INT_LITERAL)).val;
			match(Kind.COLON);
			int val6 = ((IntLitToken) match(Kind.INT_LITERAL)).val;
			if (flag) {
				calendar.set(val1, val2, val3, val4, val5, val6);
			}
			else {
				calendar.set(val4, val2, val3, val1, val5, val6);
			}
			return !flag;
		default:
			throw new SyntaxException(t, "");
		}
	}

	/* Command = BASIC event | action */
	private void basic() {
		if (t.kind == Kind.EOF) {
			executor.exeBasic();
		} else if (t.kind == Kind.EVENT || t.kind == Kind.ACTION) {
			executor.exeBasic(t.kind);
		} else {
			JOptionPane.showMessageDialog(re, "Invalid usage of BASIC");
		}
	}

	private void list() {
		if (!isKind(Kind.EOF)) {
			switch (t.kind) {
			case EVENT:
			case CONDITION:
			case ACTION:
			case RULE:
				executor.exeList(t.kind);
				break;
			default:
				JOptionPane.showMessageDialog(re, "Invalid usage of List");
				break;
			}
		} else {
			executor.exeList();
		}
	}

	public void consume() {
		t = s.next();
	}

	public boolean isKind(Kind kind) {
		if (kind == t.kind)
			return true;
		else {
			return false;
		}
	}

	public Token match(Kind kind) throws SyntaxException {
		Token curT = t;
		if (isKind(kind)) {
			consume();
			return curT;
		} else {
			throw new SyntaxException(t, "Parsing error: " + kind + " is excepted");			
		}
	}

}
